/*-------------------------------------------------------------------------
 *
 * foreign.c
 *		  支持外部数据包装器、服务器和用户映射。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  src/backend/foreign/foreign.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/reloptions.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_user_mapping.h"
#include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"


/*
 * GetForeignDataWrapper - 通过OID查找外部数据包装器。
 */
ForeignDataWrapper * GetForeignDataWrapper(Oid fc_fdwid)
{
	return GetForeignDataWrapperExtended(fc_fdwid, 0);
}


/*
 * GetForeignDataWrapperExtended - 通过OID查找外部数据包装器。
 * 如果flags使用FDW_MISSING_OK，当对象无法找到时返回NULL，而不是引发错误。
 */
ForeignDataWrapper * GetForeignDataWrapperExtended(Oid fc_fdwid, bits16 fc_flags)
{
	Form_pg_foreign_data_wrapper fc_fdwform;
	ForeignDataWrapper *fc_fdw;
	Datum		fc_datum;
	HeapTuple	fc_tp;
	bool		fc_isnull;

	fc_tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fc_fdwid));

	if (!HeapTupleIsValid(fc_tp))
	{
		if ((fc_flags & FDW_MISSING_OK) == 0)
			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fc_fdwid);
		return NULL;
	}

	fc_fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(fc_tp);

	fc_fdw = (ForeignDataWrapper *) palloc(sizeof(ForeignDataWrapper));
	fc_fdw->fdwid = fc_fdwid;
	fc_fdw->owner = fc_fdwform->fdwowner;
	fc_fdw->fdwname = pstrdup(NameStr(fc_fdwform->fdwname));
	fc_fdw->fdwhandler = fc_fdwform->fdwhandler;
	fc_fdw->fdwvalidator = fc_fdwform->fdwvalidator;

	/* 提取fdwoptions */
	fc_datum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID,
							fc_tp,
							Anum_pg_foreign_data_wrapper_fdwoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_fdw->options = NIL;
	else
		fc_fdw->options = untransformRelOptions(fc_datum);

	ReleaseSysCache(fc_tp);

	return fc_fdw;
}


/*
 * GetForeignDataWrapperByName - 通过名称查找外部数据包装器定义。
 */
ForeignDataWrapper * GetForeignDataWrapperByName(const char *fc_fdwname, bool fc_missing_ok)
{
	Oid			fc_fdwId = get_foreign_data_wrapper_oid(fc_fdwname, fc_missing_ok);

	if (!OidIsValid(fc_fdwId))
		return NULL;

	return GetForeignDataWrapper(fc_fdwId);
}


/*
 * GetForeignServer - 查找外部服务器定义。
 */
ForeignServer * GetForeignServer(Oid fc_serverid)
{
	return GetForeignServerExtended(fc_serverid, 0);
}


/*
 * GetForeignServerExtended - 查找外部服务器定义。如果
 * flags使用FSV_MISSING_OK，当对象无法找到时返回NULL
 * 而不是引发错误。
 */
ForeignServer * GetForeignServerExtended(Oid fc_serverid, bits16 fc_flags)
{
	Form_pg_foreign_server fc_serverform;
	ForeignServer *fc_server;
	HeapTuple	fc_tp;
	Datum		fc_datum;
	bool		fc_isnull;

	fc_tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(fc_serverid));

	if (!HeapTupleIsValid(fc_tp))
	{
		if ((fc_flags & FSV_MISSING_OK) == 0)
			elog(ERROR, "cache lookup failed for foreign server %u", fc_serverid);
		return NULL;
	}

	fc_serverform = (Form_pg_foreign_server) GETSTRUCT(fc_tp);

	fc_server = (ForeignServer *) palloc(sizeof(ForeignServer));
	fc_server->serverid = fc_serverid;
	fc_server->servername = pstrdup(NameStr(fc_serverform->srvname));
	fc_server->owner = fc_serverform->srvowner;
	fc_server->fdwid = fc_serverform->srvfdw;

	/* 提取服务器类型 */
	fc_datum = SysCacheGetAttr(FOREIGNSERVEROID,
							fc_tp,
							Anum_pg_foreign_server_srvtype,
							&fc_isnull);
	fc_server->servertype = fc_isnull ? NULL : TextDatumGetCString(fc_datum);

	/* 提取服务器版本 */
	fc_datum = SysCacheGetAttr(FOREIGNSERVEROID,
							fc_tp,
							Anum_pg_foreign_server_srvversion,
							&fc_isnull);
	fc_server->serverversion = fc_isnull ? NULL : TextDatumGetCString(fc_datum);

	/* 提取srvoptions */
	fc_datum = SysCacheGetAttr(FOREIGNSERVEROID,
							fc_tp,
							Anum_pg_foreign_server_srvoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_server->options = NIL;
	else
		fc_server->options = untransformRelOptions(fc_datum);

	ReleaseSysCache(fc_tp);

	return fc_server;
}


/*
 * GetForeignServerByName - 通过名称查找外部服务器定义。
 */
ForeignServer * GetForeignServerByName(const char *fc_srvname, bool fc_missing_ok)
{
	Oid			fc_serverid = get_foreign_server_oid(fc_srvname, fc_missing_ok);

	if (!OidIsValid(fc_serverid))
		return NULL;

	return GetForeignServer(fc_serverid);
}


/*
 * GetUserMapping - 查找用户映射。
 *
 * 如果未找到供给用户的映射，我们还会查找
 * PUBLIC映射（userid == InvalidOid）。
 */
UserMapping *
GetUserMapping(Oid fc_userid, Oid fc_serverid)
{
	Datum		fc_datum;
	HeapTuple	fc_tp;
	bool		fc_isnull;
	UserMapping *fc_um;

	fc_tp = SearchSysCache2(USERMAPPINGUSERSERVER,
						 ObjectIdGetDatum(fc_userid),
						 ObjectIdGetDatum(fc_serverid));

	if (!HeapTupleIsValid(fc_tp))
	{
		/* 未找到特定用户的映射 -- 尝试PUBLIC */
		fc_tp = SearchSysCache2(USERMAPPINGUSERSERVER,
							 ObjectIdGetDatum(InvalidOid),
							 ObjectIdGetDatum(fc_serverid));
	}

	if (!HeapTupleIsValid(fc_tp))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("user mapping not found for \"%s\"",
						MappingUserName(fc_userid))));

	fc_um = (UserMapping *) palloc(sizeof(UserMapping));
	fc_um->umid = ((Form_pg_user_mapping) GETSTRUCT(fc_tp))->oid;
	fc_um->userid = fc_userid;
	fc_um->serverid = fc_serverid;

	/* 提取umoptions */
	fc_datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
							fc_tp,
							Anum_pg_user_mapping_umoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_um->options = NIL;
	else
		fc_um->options = untransformRelOptions(fc_datum);

	ReleaseSysCache(fc_tp);

	return fc_um;
}


/*
 * GetForeignTable - 通过关系oid查找外部表定义。
 */
ForeignTable *
GetForeignTable(Oid fc_relid)
{
	Form_pg_foreign_table fc_tableform;
	ForeignTable *fc_ft;
	HeapTuple	fc_tp;
	Datum		fc_datum;
	bool		fc_isnull;

	fc_tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for foreign table %u", fc_relid);
	fc_tableform = (Form_pg_foreign_table) GETSTRUCT(fc_tp);

	fc_ft = (ForeignTable *) palloc(sizeof(ForeignTable));
	fc_ft->relid = fc_relid;
	fc_ft->serverid = fc_tableform->ftserver;

	/* 提取ftoptions */
	fc_datum = SysCacheGetAttr(FOREIGNTABLEREL,
							fc_tp,
							Anum_pg_foreign_table_ftoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_ft->options = NIL;
	else
		fc_ft->options = untransformRelOptions(fc_datum);

	ReleaseSysCache(fc_tp);

	return fc_ft;
}


/*
 * GetForeignColumnOptions - 获取给定关系/attnum的attfdwoptions
 * 作为DefElem列表。
 */
List * GetForeignColumnOptions(Oid fc_relid, AttrNumber fc_attnum)
{
	List	   *fc_options;
	HeapTuple	fc_tp;
	Datum		fc_datum;
	bool		fc_isnull;

	fc_tp = SearchSysCache2(ATTNUM,
						 ObjectIdGetDatum(fc_relid),
						 Int16GetDatum(fc_attnum));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
			 fc_attnum, fc_relid);
	fc_datum = SysCacheGetAttr(ATTNUM,
							fc_tp,
							Anum_pg_attribute_attfdwoptions,
							&fc_isnull);
	if (fc_isnull)
		fc_options = NIL;
	else
		fc_options = untransformRelOptions(fc_datum);

	ReleaseSysCache(fc_tp);

	return fc_options;
}


/*
 * GetFdwRoutine - 调用指定的外部数据包装器处理程序例程
 * 以获取其FdwRoutine结构。
 */
FdwRoutine * GetFdwRoutine(Oid fc_fdwhandler)
{
	Datum		fc_datum;
	FdwRoutine *fc_routine;

	/* 检查对外部表的访问是否受到限制 */
	if (unlikely((restrict_nonsystem_relation_kind & RESTRICT_RELKIND_FOREIGN_TABLE) != 0))
	{
		/* 不能有内置的FDW处理程序 */
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("access to non-system foreign table is restricted")));
	}

	fc_datum = OidFunctionCall0(fc_fdwhandler);
	fc_routine = (FdwRoutine *) DatumGetPointer(fc_datum);

	if (fc_routine == NULL || !IsA(fc_routine, FdwRoutine))
		elog(ERROR, "foreign-data wrapper handler function %u did not return an FdwRoutine struct",
			 fc_fdwhandler);

	return fc_routine;
}


/*
 * GetForeignServerIdByRelId - 查找给定外部表的外部服务器
 * 并返回其OID。
 */
Oid GetForeignServerIdByRelId(Oid fc_relid)
{
	HeapTuple	fc_tp;
	Form_pg_foreign_table fc_tableform;
	Oid			fc_serverid;

	fc_tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for foreign table %u", fc_relid);
	fc_tableform = (Form_pg_foreign_table) GETSTRUCT(fc_tp);
	fc_serverid = fc_tableform->ftserver;
	ReleaseSysCache(fc_tp);

	return fc_serverid;
}


/*
 * GetFdwRoutineByServerId - 查找给定外部服务器的外部数据包装器
 * 的处理程序，并检索其FdwRoutine结构。
 */
FdwRoutine * GetFdwRoutineByServerId(Oid fc_serverid)
{
	HeapTuple	fc_tp;
	Form_pg_foreign_data_wrapper fc_fdwform;
	Form_pg_foreign_server fc_serverform;
	Oid			fc_fdwid;
	Oid			fc_fdwhandler;

	/* 获取服务器的外部数据包装器OID。 */
	fc_tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(fc_serverid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for foreign server %u", fc_serverid);
	fc_serverform = (Form_pg_foreign_server) GETSTRUCT(fc_tp);
	fc_fdwid = fc_serverform->srvfdw;
	ReleaseSysCache(fc_tp);

	/* 获取FDW的处理函数OID。 */
	fc_tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fc_fdwid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fc_fdwid);
	fc_fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(fc_tp);
	fc_fdwhandler = fc_fdwform->fdwhandler;

	/* 如果FDW已设置为NO HANDLER则发出警告。 */
	if (!OidIsValid(fc_fdwhandler))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("foreign-data wrapper \"%s\" has no handler",
						NameStr(fc_fdwform->fdwname))));

	ReleaseSysCache(fc_tp);

	/* 最后，调用处理函数。 */
	return GetFdwRoutine(fc_fdwhandler);
}


/*
 * GetFdwRoutineByRelId - 查找给定外部表的外部数据包装器
 * 的处理程序，并检索其FdwRoutine结构。
 */
FdwRoutine * GetFdwRoutineByRelId(Oid fc_relid)
{
	Oid			fc_serverid;

	/* 获取外部表的服务器OID。 */
	fc_serverid = GetForeignServerIdByRelId(fc_relid);

	/* 现在检索服务器的FdwRoutine结构。 */
	return GetFdwRoutineByServerId(fc_serverid);
}

/*
 * GetFdwRoutineForRelation - 查找给定外部表的外部数据包装器
 * 的处理程序，并检索其FdwRoutine结构。
 *
 * 此函数优于GetFdwRoutineByRelId，因为它在relcache项中缓存
 * 数据，从而节省了一些目录查找的时间。
 *
 * 如果makecopy为true，则返回的数据是在调用者的内存上下文中新分配的。
 * 否则，它是指向relcache数据的指针，
 * 该数据将在任何relcache重置中丢失 --- 因此不要长时间依赖它。
 */
FdwRoutine * GetFdwRoutineForRelation(Relation fc_relation, bool fc_makecopy)
{
	FdwRoutine *fc_fdwroutine;
	FdwRoutine *fc_cfdwroutine;

	if (fc_relation->rd_fdwroutine == NULL)
	{
		/* 通过查阅目录和FDW代码获取信息 */
		fc_fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(fc_relation));

		/* 将数据保存在CacheMemoryContext中以供后续重用 */
		fc_cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
														sizeof(FdwRoutine));
		memcpy(fc_cfdwroutine, fc_fdwroutine, sizeof(FdwRoutine));
		fc_relation->rd_fdwroutine = fc_cfdwroutine;

		/* 返回本地分配的副本，无论是否需要复制 */
		return fc_fdwroutine;
	}

	/* 我们有有效的缓存数据 --- 调用者是否想要一个副本？ */
	if (fc_makecopy)
	{
		fc_fdwroutine = (FdwRoutine *) palloc(sizeof(FdwRoutine));
		memcpy(fc_fdwroutine, fc_relation->rd_fdwroutine, sizeof(FdwRoutine));
		return fc_fdwroutine;
	}

	/* 只需要一个短期引用，所以只需返回缓存副本 */
	return fc_relation->rd_fdwroutine;
}


/*
 * IsImportableForeignTable - 过滤IMPORT FOREIGN SCHEMA的表名
 *
 * 如果给定的表名应根据声明的导入过滤选项被导入，则返回true。
 */
bool IsImportableForeignTable(const char *fc_tablename,
						 ImportForeignSchemaStmt *fc_stmt)
{
	ListCell   *fc_lc;

	switch (fc_stmt->list_type)
	{
		case FDW_IMPORT_SCHEMA_ALL:
			return true;

		case FDW_IMPORT_SCHEMA_LIMIT_TO:
			foreach(fc_lc, fc_stmt->table_list)
			{
				RangeVar   *fc_rv = (RangeVar *) lfirst(fc_lc);

				if (strcmp(fc_tablename, fc_rv->relname) == 0)
					return true;
			}
			return false;

		case FDW_IMPORT_SCHEMA_EXCEPT:
			foreach(fc_lc, fc_stmt->table_list)
			{
				RangeVar   *fc_rv = (RangeVar *) lfirst(fc_lc);

				if (strcmp(fc_tablename, fc_rv->relname) == 0)
					return false;
			}
			return true;
	}
	return false;				/* 不应该到这里 */
}


/*
 * pg_options_to_table - 将选项数组转换为名称/值表
 *
 * 这对于提供信息_schema和pg_dump的详细信息非常有用。
 */
Datum pg_options_to_table(PG_FUNCTION_ARGS)
{
	Datum		fc_array = PG_GETARG_DATUM(0);
	ListCell   *fc_cell;
	List	   *fc_options;
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;

	fc_options = untransformRelOptions(fc_array);
	fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;

	/* 准备结果集 */
	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);

	foreach(fc_cell, fc_options)
	{
		DefElem    *fc_def = lfirst(fc_cell);
		Datum		fc_values[2];
		bool		fc_nulls[2];

		fc_values[0] = CStringGetTextDatum(fc_def->defname);
		fc_nulls[0] = false;
		if (fc_def->arg)
		{
			fc_values[1] = CStringGetTextDatum(strVal(fc_def->arg));
			fc_nulls[1] = false;
		}
		else
		{
			fc_values[1] = (Datum) 0;
			fc_nulls[1] = true;
		}
		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	return (Datum) 0;
}


/*
 * 描述postgresql FDW、服务器和用户映射的有效选项。
 */
struct ConnectionOption
{
	const char *optname;
	Oid			optcontext;		/* 选项可能出现的目录的 Oid */
};

/*
 * 从fe-connect.c PQconninfoOptions复制而来。
 *
 * 此列表很小 - 如果保持如此，则无需使用二分搜索。
 */
static const struct ConnectionOption libpq_conninfo_options[] = {
	{"authtype", ForeignServerRelationId},
	{"service", ForeignServerRelationId},
	{"user", UserMappingRelationId},
	{"password", UserMappingRelationId},
	{"connect_timeout", ForeignServerRelationId},
	{"dbname", ForeignServerRelationId},
	{"host", ForeignServerRelationId},
	{"hostaddr", ForeignServerRelationId},
	{"port", ForeignServerRelationId},
	{"tty", ForeignServerRelationId},
	{"options", ForeignServerRelationId},
	{"requiressl", ForeignServerRelationId},
	{"sslmode", ForeignServerRelationId},
	{"gsslib", ForeignServerRelationId},
	{NULL, InvalidOid}
};


/*
 * 检查提供的选项是否是libpq连接信息选项之一。
 * context是选项来自的目录的Oid，如果我们不关心，则为0。
 */
static bool fc_is_conninfo_option(const char *fc_option, Oid fc_context)
{
	const struct ConnectionOption *fc_opt;

	for (fc_opt = libpq_conninfo_options; fc_opt->optname; fc_opt++)
		if (fc_context == fc_opt->optcontext && strcmp(fc_opt->optname, fc_option) == 0)
			return true;
	return false;
}


/*
 * 验证提供给SERVER或USER MAPPING的通用选项。
 * 如果选项或其值被认为是无效的，则引发一个错误。
 *
 * 有效的服务器选项是所有libpq连接信息选项，除了
 * 用户和密码 - 这些只能出现在USER MAPPING选项中。
 *
 * 警告：此函数已弃用，现在仅用于测试目的，因为它所知道的选项列表
 * 不一定与您可能使用的任何libpq实例所知道的相符。
 * 请直接咨询libpq。
 */
Datum postgresql_fdw_validator(PG_FUNCTION_ARGS)
{
	List	   *fc_options_list = untransformRelOptions(PG_GETARG_DATUM(0));
	Oid			fc_catalog = PG_GETARG_OID(1);

	ListCell   *fc_cell;

	foreach(fc_cell, fc_options_list)
	{
		DefElem    *fc_def = lfirst(fc_cell);

		if (!fc_is_conninfo_option(fc_def->defname, fc_catalog))
		{
			const struct ConnectionOption *fc_opt;
			StringInfoData fc_buf;

			/*
			 * 指定了未知选项，进行投诉。提供与该对象有效选项列表的提示。
			 */
			initStringInfo(&fc_buf);
			for (fc_opt = libpq_conninfo_options; fc_opt->optname; fc_opt++)
				if (fc_catalog == fc_opt->optcontext)
					appendStringInfo(&fc_buf, "%s%s", (fc_buf.len > 0) ? ", " : "",
									 fc_opt->optname);

			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("invalid option \"%s\"", fc_def->defname),
					 fc_buf.len > 0
					 ? errhint("Valid options in this context are: %s",
							   fc_buf.data)
					 : errhint("There are no valid options in this context.")));

			PG_RETURN_BOOL(false);
		}
	}

	PG_RETURN_BOOL(true);
}


/*
 * get_foreign_data_wrapper_oid - 给定FDW名称，查找OID
 *
 * 如果missing_ok为false，则在找不到名称时抛出错误。如果为true，则只
 * 返回InvalidOid。
 */
Oid get_foreign_data_wrapper_oid(const char *fc_fdwname, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(FOREIGNDATAWRAPPERNAME,
						  Anum_pg_foreign_data_wrapper_oid,
						  CStringGetDatum(fc_fdwname));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("foreign-data wrapper \"%s\" does not exist",
						fc_fdwname)));
	return fc_oid;
}


/*
 * get_foreign_server_oid - 给定服务器名称，查找OID
 *
 * 如果missing_ok为false，则在找不到名称时抛出错误。如果为true，则只
 * 返回InvalidOid。
 */
Oid get_foreign_server_oid(const char *fc_servername, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(FOREIGNSERVERNAME, Anum_pg_foreign_server_oid,
						  CStringGetDatum(fc_servername));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("server \"%s\" does not exist", fc_servername)));
	return fc_oid;
}

/*
 * 获取给定连接关系的现有本地路径的副本。
 *
 * 这个函数通常有助于获取用于EPQ检查的备用本地路径。
 *
 * 目前，这个函数只支持无参数的外部连接，所以我们
 * 只在给定的路径列表中搜索无参数的路径。由于我们
 * 正在寻找一个可以用于构建外部连接的替代本地
 * 计划的路径，因此我们仅查找MergeJoin、HashJoin或NestLoop
 * 路径。
 *
 * 如果所选路径的内部或外部子路径是ForeignScan，我们
 * 将其替换为其外部子路径。出于这个原因，还有因为规划者可能会稍后释放原始路径， 
 * 本函数返回的路径是原始路径的浅复制。没有必要复制
 * 子结构，因此我们不这样做。
 *
 * 由于使用此路径创建的计划预计只用于
 * 执行EPQ检查，路径的效率不是问题。但由于
 * RelOptInfo中的路径列表无论如何都是按总成本排序的，因此我们可能会
 * 选择最有效的路径，这样做是最好的。
 */
Path * GetExistingLocalJoinPath(RelOptInfo *fc_joinrel)
{
	ListCell   *fc_lc;

	Assert(IS_JOIN_REL(fc_joinrel));

	foreach(fc_lc, fc_joinrel->pathlist)
	{
		Path	   *fc_path = (Path *) lfirst(fc_lc);
		JoinPath   *fc_joinpath = NULL;

		/* 跳过参数化路径。 */
		if (fc_path->param_info != NULL)
			continue;

		switch (fc_path->pathtype)
		{
			case T_HashJoin:
				{
					HashPath   *fc_hash_path = makeNode(HashPath);

					memcpy(fc_hash_path, fc_path, sizeof(HashPath));
					fc_joinpath = (JoinPath *) fc_hash_path;
				}
				break;

			case T_NestLoop:
				{
					NestPath   *fc_nest_path = makeNode(NestPath);

					memcpy(fc_nest_path, fc_path, sizeof(NestPath));
					fc_joinpath = (JoinPath *) fc_nest_path;
				}
				break;

			case T_MergeJoin:
				{
					MergePath  *fc_merge_path = makeNode(MergePath);

					memcpy(fc_merge_path, fc_path, sizeof(MergePath));
					fc_joinpath = (JoinPath *) fc_merge_path;
				}
				break;

			default:

				/*
				 * 跳过其他任何内容。我们不知道相应
				 * 的计划是否会从基本关系的整行引用构建输出行
				 * 并执行EPQ检查。
				 */
				break;
		}

		/* 这个路径对我们来说不好，检查下一个。 */
		if (!fc_joinpath)
			continue;

		/*
		 * 如果内部或外部路径是对应于
		 * 下推连接的ForeignPath，则用fdw_outerpath替换它，
		 * 以便我们维护完全由本地连接
		 * 策略构建的EPQ检查路径。
		 */
		if (IsA(fc_joinpath->outerjoinpath, ForeignPath))
		{
			ForeignPath *fc_foreign_path;

			fc_foreign_path = (ForeignPath *) fc_joinpath->outerjoinpath;
			if (IS_JOIN_REL(fc_foreign_path->path.parent))
				fc_joinpath->outerjoinpath = fc_foreign_path->fdw_outerpath;
		}

		if (IsA(fc_joinpath->innerjoinpath, ForeignPath))
		{
			ForeignPath *fc_foreign_path;

			fc_foreign_path = (ForeignPath *) fc_joinpath->innerjoinpath;
			if (IS_JOIN_REL(fc_foreign_path->path.parent))
				fc_joinpath->innerjoinpath = fc_foreign_path->fdw_outerpath;
		}

		return (Path *) fc_joinpath;
	}
	return NULL;
}
